/**
 *    Copyright (c) 2021 WANGFEIHU
 *    SSO-PLUS is licensed under Mulan PSL v2.
 *    You can use this software according to the terms and conditions of the Mulan PSL v2.
 *    You may obtain a copy of Mulan PSL v2 at:
 *             http://license.coscl.org.cn/MulanPSL2
 *    THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 *    See the Mulan PSL v2 for more details.
 */
package cn.codinglives.ssoplus.config;

import cn.codinglives.ssoplus.entity.SsoMain;
import cn.codinglives.ssoplus.entity.SsoSaml;
import cn.codinglives.ssoplus.service.SsoMainService;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.opensaml.saml2.metadata.provider.*;
import org.opensaml.xml.parse.StaticBasicParserPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.saml.metadata.CachingMetadataManager;
import org.springframework.security.saml.metadata.ExtendedMetadata;
import org.springframework.security.saml.metadata.ExtendedMetadataDelegate;

import javax.annotation.Resource;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;

@Configuration
public class MetaDataConfig implements InitializingBean, DisposableBean {

    private final static Logger logger = LoggerFactory.getLogger(MetaDataConfig.class);
    @Resource
    private SsoMainService ssoConfService;

    @Resource
    private StaticBasicParserPool parserPool;

    private MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager;

    @Bean
    public CachingMetadataManager metadataManager() throws MetadataProviderException, IOException {
        List<MetadataProvider> providers = new ArrayList<>();
        logger.info("start load saml sso conf...");
        List<SsoMain> confList = ssoConfService.list("saml");
        for (SsoMain conf: confList) {
            logger.info("tenant-conf:{}", conf.getTenantCode());
            ExtendedMetadata extendedMetadata = createExtendedMetadata();
            extendedMetadata.setAlias(conf.getTenantCode());
            SsoSaml saml = ssoConfService.getSsoSamlByMainId(conf.getId());
            MetadataProvider metadataProvider = createMetadataProvider(conf.getTenantCode(), saml.getMetaFilePath(), parserPool, extendedMetadata);
            providers.add(metadataProvider);
        }
        return new CachingMetadataManager(providers);
    }

//    @Bean(initMethod = "initialize")
//    public StaticBasicParserPool parserPool() {
//        return new StaticBasicParserPool();
//    }

    @Bean
    public HttpClient httpClient() {
        return new HttpClient(this.multiThreadedHttpConnectionManager);
    }



    private ExtendedMetadata createExtendedMetadata() {
        ExtendedMetadata extendedMetadata = new ExtendedMetadata();
        extendedMetadata.setIdpDiscoveryEnabled(false);
        extendedMetadata.setSigningAlgorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
        extendedMetadata.setSignMetadata(true);
        extendedMetadata.setEcpEnabled(true);
        return extendedMetadata;
    }

    private ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
            new BasicThreadFactory.Builder().namingPattern("timer-schedule-pool-%d").daemon(true).build());


    /**
     * create meta provider with tenant sso conf.
     * @param tenantCode
     * @param metaDataPath
     * @param parserPool
     * @param extendedMetadata
     * @return
     * @throws IOException
     * @throws MetadataProviderException
     */
    private MetadataProvider createMetadataProvider(String tenantCode, String metaDataPath,
                                                    StaticBasicParserPool parserPool, ExtendedMetadata extendedMetadata) throws IOException, MetadataProviderException {
        AbstractMetadataProvider metadataProvider;
        if(metaDataPath.startsWith("http")) {
            Timer timer = new Timer();
            metadataProvider = new HTTPMetadataProvider(timer, httpClient(), metaDataPath);
        } else {
            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(metaDataPath);
            File metaFile = new File(tenantCode + ".xml");
            if (!metaFile.exists()) {
                OutputStream os = new BufferedOutputStream(new FileOutputStream(metaFile));
                IOUtils.copy(inputStream, os);
                os.flush();
                os.close();
                inputStream.close();
            }
            metadataProvider = new FilesystemMetadataProvider(metaFile);
        }

        metadataProvider.setParserPool(parserPool);
        ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(metadataProvider, extendedMetadata);
        extendedMetadataDelegate.setMetadataTrustCheck(true);
        extendedMetadataDelegate.setMetadataRequireSignature(false);
        extendedMetadataDelegate.setForceMetadataRevocationCheck(false);
        return extendedMetadataDelegate;
    }

    @Override
    public void afterPropertiesSet() {
        this.multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager();
    }

    @Override
    public void destroy() {
        this.multiThreadedHttpConnectionManager.shutdown();
    }
}
